package link.luyu.plugin.brochain.web3;

import io.reactivex.Flowable;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import link.luyu.plugin.brochain.web3.methods.request.CallTransaction;
import link.luyu.plugin.brochain.web3.methods.request.Transaction;
import link.luyu.plugin.brochain.web3.methods.response.*;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.utils.Async;

public class Web3 {

    public static final int DEFAULT_BLOCK_TIME = 3 * 1000;

    protected final Web3jService web3jService;
    private final Web3Rx web3Rx;
    private final long blockTime;
    private final ScheduledExecutorService scheduledExecutorService;

    public Web3(Web3jService web3jService) {
        this(web3jService, DEFAULT_BLOCK_TIME, Async.defaultExecutorService());
    }

    public Web3(
            Web3jService web3jService,
            long pollingInterval,
            ScheduledExecutorService scheduledExecutorService) {
        this.web3jService = web3jService;
        this.web3Rx = new Web3Rx(this, scheduledExecutorService);
        this.blockTime = pollingInterval;
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public Request<?, EthBlockNumber> ethBlockNumber() {
        return new Request<>(
                "eth_blockNumber",
                Collections.<String>emptyList(),
                web3jService,
                EthBlockNumber.class);
    }

    public Request<?, BrochainSendTransaction> brochainSendTransaction(Transaction transaction) {
        return new Request<>(
                "brochain_sendTransaction",
                Arrays.asList(transaction),
                web3jService,
                BrochainSendTransaction.class);
    }

    public Request<?, BrochainSendTransaction> brochainSendRawTransaction(
            String signedTransactionData) {
        return new Request<>(
                "brochain_sendRawTransaction",
                Arrays.asList(signedTransactionData),
                web3jService,
                BrochainSendTransaction.class);
    }

    public Request<?, BrochainCall> brochainCall(
            CallTransaction transaction,
            String regionId,
            DefaultBlockParameter defaultBlockParameter) {
        return new Request<>(
                "brochain_call",
                Arrays.asList(transaction, regionId, defaultBlockParameter),
                web3jService,
                BrochainCall.class);
    }

    public Request<?, BrochainBlock> brochainGetBlock(DefaultBlockParameter defaultBlockParameter) {
        return new Request<>(
                "brochain2_getBlockByNumber",
                Arrays.asList(defaultBlockParameter.getValue()),
                web3jService,
                BrochainBlock.class);
    }

    public Request<?, BrochainPage> brochainGetPage(
            String regionId, String pageHash, boolean includeTransactions) {
        return new Request<>(
                "brochain_getRegionPageByHash",
                Arrays.asList(regionId, pageHash, includeTransactions),
                web3jService,
                BrochainPage.class);
    }

    public Request<?, BrochainTransaction> brochainGetTransaction(
            String regionId, String transactionHash) {
        return new Request<>(
                "brochain_getTransactionByHash",
                Arrays.asList(regionId, transactionHash),
                web3jService,
                BrochainTransaction.class);
    }

    public Request<?, BrochainGetTransactionReceipt> brochainGetTransactionReceipt(
            String regionId, String transactionHash) {
        return new Request<>(
                "brochain_getTransactionReceiptByHash",
                Arrays.asList(regionId, transactionHash),
                web3jService,
                BrochainGetTransactionReceipt.class);
    }

    public Request<?, BrochainVotes> brochainGetVotes(DefaultBlockParameter defaultBlockParameter) {
        return new Request<>(
                "brochain2_getVotesByBlockNumber",
                Arrays.asList(defaultBlockParameter),
                web3jService,
                BrochainVotes.class);
    }

    public Flowable<BigInteger> blockNumberFlowable() {
        return web3Rx.blockNumberFlowable(blockTime);
    }

    public Flowable<Block> blockFlowable() {
        return web3Rx.blockFlowable(blockTime);
    }

    public Flowable<Optional<Page>> pageFlowable(String regionId) {
        return web3Rx.pageFlowable(regionId, blockTime);
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
        try {
            web3jService.close();
        } catch (IOException e) {
            throw new RuntimeException("Failed to close web3 service", e);
        }
    }
}
